/*
 * #%L
 * java model
 * %%
 * Copyright (C) 2009 - 2011 prefabware.com Stefan Isele
 * %%
 * All Rights Reserved.
 * This program is dual-licensed under the GNU General Public license
 * or a commercial license.
 * You may select to use one or the other of these licenses.
 * To obtain a commercial license please contact Stefan Isele at www.stefan-isele.de
 * Any modifications to this file must keep this entire header intact.
 * Redistribution, except as permitted by the selected license, is prohibited.
 * #L%
 */
package com.prefabware.jmodel.beans;

import java.util.List;

import com.prefabware.commons.CollectionUtil;
import com.prefabware.commons.Condition;
import com.prefabware.commons.JavaUtil;
import com.prefabware.commons.collection.Filter;
import com.prefabware.jmodel.JArgument;
import com.prefabware.jmodel.JClass;
import com.prefabware.jmodel.JConstructor;
import com.prefabware.jmodel.JConstructorDeclaration;
import com.prefabware.jmodel.JField;
import com.prefabware.jmodel.JMember;
import com.prefabware.jmodel.JMethod;
import com.prefabware.jmodel.JMethodDeclaration;
import com.prefabware.jmodel.JModifiers;
import com.prefabware.jmodel.JType;
import com.prefabware.jmodel.JTypeUtil;
import com.prefabware.jmodel.JTypeWithFields;
import com.prefabware.jmodel.JVisibility;
import com.prefabware.jmodel.code.JBlock;
import com.prefabware.jmodel.code.JCodeOptions;
import com.prefabware.jmodel.expression.JAssignment;
import com.prefabware.jmodel.expression.JLeftHandSideExpression;
import com.prefabware.jmodel.typefactory.TypeFactory;

public class BeanUtil {
	public static final Filter<JMember> FILTER_INCLUDING_GETTER_ONLY = new Filter<JMember>() {

		@Override
		public boolean includes(JMember value) {
			return value instanceof Getter;
		}
	};

	public static Property createProperty(JField field, JModifiers modifiers,
			JVisibility visibility) {
		Getter getter = createGetter(field, modifiers, visibility);
		Setter setter = createSetter(field, modifiers, visibility);
		return new Property(field, getter, setter);
	}

	/**
	 * creates getter for all fields of the class, without modifiers and public
	 * visibility
	 * 
	 * @param cls
	 */
	public static void addPublicGetterForAllFields(JTypeWithFields cls) {
		addGetterForAllFields(cls, JModifiers.NONE, JVisibility.PUBLIC);
	}

	public static void addGetterForAllFields(JTypeWithFields cls,
			JModifiers modifiers, JVisibility visibility) {
		for (JField field : cls.getFields()) {
			addGetterForField(cls, field, modifiers, visibility);
		}
	}

	public static Getter addGetterForField(JTypeWithFields cls, JField field,
			JModifiers modifiers, JVisibility visibility) {
		Getter getter = createGetter(field, modifiers, visibility);
		cls.add(getter);
		return getter;
	}

	/**
	 * @param field
	 * @param modifiers
	 * @param visibility
	 * @return
	 */
	public static Getter createGetter(JField field, JModifiers modifiers,
			JVisibility visibility) {
		String name = JavaUtil.getGetterName(field.getName(), null);
		JMethodDeclaration jMemberDeclaration = JTypeUtil
				.newMethodDeclaration(
						name,
						field.getDeclaration().getType(),
						field.getDeclaringType(),
						modifiers,
						visibility,
						field.getDeclaration().getTypeArguments()
								.toArray(new JType[0]));
		Getter getter = newGetter(field, jMemberDeclaration);
		return getter;
	}

	/**
	 * creates setter for all fields of the class, without modifiers and public
	 * visibility
	 * 
	 * @param cls
	 */
	public static void addPublicSetterForAllFields(JTypeWithFields cls) {
		addSetterForAllFields(cls, JModifiers.NONE, JVisibility.PUBLIC);
	}

	public static void addSetterForAllFields(JTypeWithFields cls,
			JModifiers modifiers, JVisibility visibility) {
		for (JField field : cls.getFields()) {
			addSetterForField(cls, field, modifiers, visibility);
		}
	}

	public static Setter addSetterForField(JTypeWithFields cls, JField field,
			JModifiers modifiers, JVisibility visibility) {
		Setter setter = createSetter(field, modifiers, visibility);
		cls.add(setter);
		return setter;
	}

	/**
	 * @param field
	 * @param modifiers
	 * @param visibility
	 * @return
	 */
	public static Setter createSetter(JField field, JModifiers modifiers,
			JVisibility visibility) {
		String name = JavaUtil.getSetterName(field.getName());
		//the return type of a setter is void without type arguements
		JMethodDeclaration setterDeclaration = JTypeUtil.newMethodDeclaration(
				name, TypeFactory.void_, field.getDeclaringType(), modifiers,
				visibility);
		Setter setter = newSetter(field, setterDeclaration);
		return setter;
	}

	public static Setter addPublicSetterForField(JTypeWithFields cls,
			JField field) {
		return addSetterForField(cls, field, JModifiers.NONE,
				JVisibility.PUBLIC);
	}

	public static Getter addPublicGetterForField(JTypeWithFields cls,
			JField field) {
		return addGetterForField(cls, field, JModifiers.NONE,
				JVisibility.PUBLIC);
	}

	/**
	 * use {@link #createAllFieldConstructor(JClass, JVisibility)} this method
	 * is a bad mixture of create and add so it will be removed
	 * 
	 * 
	 * 
	 * adds a constructor with a parameter for every field of the class each
	 * parameter will be shifted into the according field in the constructor
	 * 
	 * @param cls
	 *            the class the constructor will be added to
	 * @param visibility
	 *            the visibility eg. public
	 * @return
	 */
	// TODO remove
	@Deprecated()
	public static JConstructor addAllFieldConstructor(JClass cls,
			JVisibility visibility) {
		JConstructor con = createAllFieldConstructor(cls, visibility);
		cls.add(con);
		return con;
	}

	/**
	 * creates a new constructor. does NOT add it to the given JClass
	 * 
	 * @param cls
	 * @param visibility
	 * @return
	 */
	public static JConstructor createAllFieldConstructor(JClass cls,
			JVisibility visibility) {
		// create a default-constructor
		JConstructorDeclaration dec = new JConstructorDeclaration(
				JConstructor.getQualifiedName(cls), cls, JModifiers.NONE,
				visibility, null);
		JConstructor con = new JConstructor(dec, cls);
		con.setDeclaringType(cls);
		JBlock block = new JBlock();
		con.add(block);

		for (final JField field : cls.getFields()) {
			// create a parameter for every field of the class
			JArgument parm = JTypeUtil.newParameter(true, field.getName(),
					field.getType(), dec, field.getDeclaration()
							.getTypeArguments().toArray(new JType[0]));
			JLeftHandSideExpression thisField = new JLeftHandSideExpression() {

				@Override
				public String asCode(JCodeOptions options) {
					return "this." + field.getName();
				}
			};
			con.getDeclaration().getSignature().add(parm);
			JAssignment assignment = new JAssignment(thisField, parm);
			block.add(assignment);
		}
		return con;
	}

	public static Getter findGetter(JType type, final JField field) {
		Filter<? super JMethod> filter = new Filter<JMethod>() {

			@Override
			public boolean includes(JMethod value) {
				if (!(value instanceof Getter)) {
					return false;
				}
				Getter getter = (Getter) value;
				if (getter.getField().equals(field)) {
					return true;
				}
				return false;
			}
		};
		List<JMethod> filtered = CollectionUtil.filter(type.getMethods(),
				filter);
		Condition.isTrue(filtered.size() == 1, String
				.format("should be found exactly one getter, actual found %s",
						filtered));
		Condition
				.isTrue(filtered.get(0) instanceof Getter, String.format(
						"expecting to find a getter, actual found %s",
						filtered.get(0)));
		return (Getter) filtered.get(0);

	}

	public static Getter newGetter(JField field,
			JMethodDeclaration jMemberDeclaration) {
		Getter getter = new Getter(jMemberDeclaration, field);
		return getter;
	}

	public static Setter newSetter(JField field, JMethodDeclaration declaration) {
		JArgument parm = JTypeUtil.newParameter(true, field.getName(),
				field.getType(), declaration, field.getDeclaration()
						.getTypeArguments().toArray(new JType[0]));
		Setter setter = new Setter(declaration, field);
		setter.getDeclaration().getSignature().add(parm);
		return setter;
	}
}
